
let isDone:boolean = false;

console.log(isDone);

let arr2: Array<number> =  [1,2,3,4,5,6,7];

let obj1:{ [key: string]:any } = {}

// 枚举
enum Gender {
  F = '男',
  M = '女'
}
console.log(Gender);
console.log(Gender.F)
console.log(Gender[0]);

/** 断言
 * 类型断言有两种形式，其一是‘尖括号’语法,另一种是as语法
  1.  <类型>变量
  2.  值 as 类型 
**/
let str2:number|string = 'burt'
if((<string>str2).length){
  console.log((str2 as string).length, 111)
}else{
  console.log(str2.toString().length, 222)
}
// ts中定义的对象如何添加属性
let json1:{ [key: string] : any } = {}
let json2:Record<string, any> = {}


// 接口
interface Person {
  readonly name: string
  label: string
  [key: string]: string
}
let arr3:Person = {
  name: '2',
  label: '大佬',
  age: '防抖'
}
// arr3.name = 'burtyang' 会报错，因为name是只读属性

//定义一个接口
interface IPerson {
	firstName: string
	lastName: string
}
//输出姓名
function showFullName(person: IPerson){
	return person.firstName + person.lastName
}

const burt = {
	firstName: '杨',
	lastName: '利龙'
}
console.log(showFullName(burt))

class Person2 {
	firstName: string //姓氏
	lastName: string //名字
	fullName: string
	constructor(firstName:string,lastName:string){
		this.firstName = firstName
		this.lastName = lastName
		this.fullName = this.firstName + '-' + this.lastName
	}
}
const person = new Person2('yang', 'burt')
console.log(showFullName(person))

/** 函数类型
 * 1.  通过接口的方式作为函数的类型来使用
 * 2.  定义一个接口，用来作为某个函数的类型使用
 * 3.  定一个函数，该类型就是上面定义的接口
 * **/
interface ISerachFunc {
  //定义一个调用签名
  (source: string, sunString: string) : boolean
}
const search: ISerachFunc = function(source1, source2) {
  return source1.indexOf(source2) != -1
}

/** 可索引的类型
 * 索引签名： 它描述了对象索引的类型，还有相应的索引返回值类型
 *  **/
//定义数组的类型
interface StringArray {
  //索引签名
  [index: number]: string;
}
const strArr: StringArray = ['burt', 'yang']
// 定义obj类型
interface StringObj {
  [key: string]: string;
}
const strObj: StringObj = {
  name: 'burtyang',
  age: '30'
}

/** 类
 * 1. 接口约束 implements
 * typeScript能够用它来明确的强制一个类去符合某种契约
* **/
//定义一个接口
interface IFly{
  //该方法没有任何实现
  fly()
}
//定义一个类，这个类的类型就是上面定义的接口（也可以理解为，IFly接口约束了当前的这个Person类）
class SuperPerson implements IFly{
  fly(){
    console.log('我会飞了，我是超人');
  }
}
(new SuperPerson()).fly();
//再定义一个接口
interface ISwim{
  swim();
}
//定义一个类，这个类的类型就是IFly和ISwim(当前这个类可以实现多个接口，一个类同时也可以被多个接口约束)
class SuperPerson2 implements IFly,ISwim{
  fly(){
    console.log('我会飞了');
  }
  swim(){
    console.log('我会游泳拉');
  }
}

/** 接口继承 extends
 * 和类一样，接口也可以相互
**/
interface Shape {
  color: string;
}
// //一个接口可以继承多个接口，创建出多个接口的合成接口。 interface Square extends A, B, C
interface Square extends Shape {
  sideLength: number;
}
let square = <Square>{}
square.color = 'blue'
square.sideLength = 10


/** 类
 * 1. 可以理解为模板，通过模板可以实例化对象，面向对象的编程思想
 * 2. 继承 extends  调用 super()，它会执行基类的构造函数
 *  **/
class BurtPerson {
  name: string
  age: number
  gender: string
  constructor(name: string, age: number, gender: string) {
    this.name = name
    this.age = age
    this.gender = gender
  }
  // 定义方法
  sayHi(word: string) {
    console.log('大家好，我是：' + word)
  } 
}
// 继承 extends
class KekePerson extends BurtPerson {
  name: string
  constructor(name: string) {
    // 调用 super()，它会执行基类的构造函数
    super(name, 18, '女')
    this.name = name
  }
  sayHi(): void {
      console.log('大家好，我是' + this.name)
  }
}

let keke = new KekePerson('张兢')
keke.sayHi()

/** 多态 
 * 父类型的引用指向了子类型的对象，不同类型的对象针对相同的方法，产生了不同的行为
 * **/
//定义一个父类
class Animal{
  name: string
  constructor(name:string){
    this.name = name
  }
  run(distance:number=0){
    console.log('跑了'+distance+'米距离')
  }
}
//定义一个子类
class Dog extends Animal{
  constructor(name:string){
    super(name)
  }
  //重写父类方法
  run(distance:number=5){
    console.log('跑了'+distance+'米距离')
  }
}
//定义一个子类
class Pig extends Animal{
  constructor(name:string){
    super(name)
  }
  //重写父类方法
  run(distance:number=10){
    console.log('跑了'+distance+'米距离')
  }
}
//实例化父类对象
const ani:Animal = new Animal('动物')
ani.run();
//实例化子类对象
const dog:Dog = new Dog('大黄');
dog.run();
//实例化子类对象
const pig:Pig = new Pig('八戒');
pig.run();

/** 存取器：让我们可以有效的控制对 对象中的成员的访问，通过getters和setter来进行操作
 *  **/
class Person3{
  firstName:string
  lastName: string
  constructor(firstName:string, lastName:string) {
    this.firstName = firstName
    this.lastName = lastName
  }
  // 读取器 -----> 负责读取数据的
  get fullName() {
    return this.firstName+'_'+this.lastName
  }
  // 设置器 --------> 负责修改数据的
  set fullName(value){
    // 拆分成姓名 和 名称，并赋值
    let arr = value.split('-')
    this.firstName = arr[0]
    this.lastName = arr[1]
  }
}
const person3:Person3 = new Person3('东方', '不败')
// 获取该属性，走到get方法
console.log(person3.fullName)
// 设置该属性，走到set方法
person3.fullName = '上官-婉儿'
console.log(person3.firstName, person3.lastName)
// person3.fullName = ['东方', '不败2']

/** 静态成员 static：静态成员在使用的时候是通过类名.的这种语法来调用的
 *  **/
class Hudie {
  static myname:string = '蝴蝶'
  static fly() {
    console.log(this.myname + '可以飞很久很久')
  }
}
console.log(Hudie.myname)
Hudie.fly()

/** 抽象类
 * 1.  抽象类作为其它派生类的基类使用，它们不能被实例化。不同于接口，抽象类可以包含成员的实现细节。
 * 2.  abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。
 * 3.  抽象类：包含抽象方法（抽象方法一般没有任何的具体内容的实现），也可以包含实例方法，抽象类是不能被实例化，为了让子类进行实例化及是心啊内部的抽象方法
 * 4.  抽象类最终目的都是为了子类服务的 
 **/
abstract class Animal2 {
  //抽象方法
  abstract eat()
  //实例方法
  sayhHi(){
    console.log('您好啊')
  }
}
class BigDoc extends Animal2 {
  eat() {
    console.log('跪着吃，舔着吃')
  }
}

/** 函数： 帮助你实现抽象层，模拟类，信息隐藏和模块
 * **/
// 1. 剩余参数... , 你可以把所有参数收集到一个变量里
// ...args:string[]  剩余的参数，放在了一个字符串的数组中，args里面
function showMsg(str:string, ...args:string[]){
  console.log(str) //a
  console.log(args) //[b, c, d]
}
showMsg('a', 'b', 'c', 'd')
// 2. 函数重载  函数名字相同，函数的参数及个数不同
// 需求：我们有一个add函数，它可以接收2个string类型的参数进行拼接，也可以接收2个number类型的参数进行相加
//函数重载声明
function add(x:string,y:string):string
function add(x:number,y:number):number
//函数声明
function add(x:string|number,y:string|number):string|number{
  if(typeof x==='string' && typeof y==='string'){
    return x + y //字符串拼接
  }else if(typeof x==='number' && typeof y==='number'){
    return x + y //数字相加
  }
}
console.log(add('诸葛', '孔明'))
console.log(add(20, 30))

/** 泛型：在定义函数、接口、类的时候不能预先确定要使用的数据的类型，而是在使用函数、接口、类的时候才能确定数据的类型
 * **/
//符合要求的泛型 <T>
function getArr<T>(value:T,count:number):T[]{
  const arr:T[] = []
  for(let i=0; i<count; i++){
      arr.push(value)
  }
  return arr
}
const arr6 = getArr<number>(100.123, 3)
const arr7 = getArr<string>('abc', 3)
// 多个泛型参数的函数：函数中有多个泛型的参
function getMsg<K, V>(value1: K, value2: V):[K,V]{
  return [value1, value2]
}
const arr1 = getMsg('burt', 100) // ['burt', 100]

interface IProps {
  name: string;
  age: number;
  sex: string;
}

// Keys 类型为 'name' | 'age' | 'sex' 组成的联合类型
type Keys = keyof IProps
// 使用 Keys 类型
function getProperty(obj: IProps, key: Keys) {
  return obj[key];
}
const person5: IProps = {
  name: "John",
  age: 30,
  sex: "male"
};

// 正确使用
getProperty(person5, "name"); // "John"


/** -------------Typescript高级应用------------- **/
/** Pick：摘取一部分类型
 * Omit：跟Pick相反，是剔除一部分
 * Exclude<UnionType, ExcludedMembers>  作用: 从联合类型UnionType中排除ExcludedMembers类型然后返回一个新类型。
 * Extract<Type, Union>  作用: 从联合类型Type中提取Union类型然后返回一个新类型。
 *  **/
interface userInfo {
  userName: string
  password: string
  id: number
  type: string
  age: number
  list: string[]
}
// 我想定义一个约束，只想要上面接口中定义好的userName和password，可以用下面的Pick摘取一部分我需要的类型
type loginDataType = Pick<userInfo, 'userName' | 'password'>
let loginData: loginDataType = {
  userName: 'burt',
  password: '123456'
}

// 使用Omit剔除一部分我不需要的类型 
type resDataType = Omit<userInfo, 'list' | 'type' | 'id'>
let resData: resDataType = {
  userName: 'burt',
  password: '123456',
  age: 18
}

type A = 'a' | 'b' | 'c'
type B = 'b' | 'c'

// 联合类型type A一个一个与type B中比较，如果A中的在B中找不到，就保留下来
type Atype = Exclude<A, B> // 'a'
let aData: Atype = 'a'

// 联合类型type A一个一个与type B中比较，如果A中的在B中能找到，就保留下来
type Btype = Extract<A, B> // 'b' | 'c'
let bData: Btype = 'b' 

/** Typescript中内置类型工具 **/
// 1. Partial接收一个泛型类型Type，并将Type所有属性都设置为可选的，返回构造的新类型。
interface User {
  name: string
  age: number
  address: string
}
const user: Partial<User> = { name: 'xiaoming' }
console.log(user)

// 2. Required接收一个泛型类型Type，并将Type所有属性都设置为必选的，返回构造的新类型（Required的作用与Partial相反）。
interface User2 {
  name?: string
  age?: number
}
const user2: Required<User2> = {
  name: 'burt',
  age: 18
}
// 3. Readonly接收一个泛型类型Type，并将Type所有属性都设置为只读的
// 4. Record构造一个对象类型，其属性键为Keys，属性值为Type。使用场景示例(创建具有一致性的字典)
interface User4 {
  name: string
  age: number
}
type UserName = 'xiaoming' | 'xiaohong' | 'xiaohuang'
const users: Record<UserName, User4> = {
  xiaoming: { name: 'ming', age: 23 },
  xiaohong: { name: 'hong', age: 24 },
  xiaohuang: { name: 'huang', age: 25 }
}
const obj4:Record<string, any> = {} // 定义一个空{}
const arr4:Record<number, any> = [] // 定义一个[]


class Person7 {
  constructor(name: string) {}
}
type PersonInstance = InstanceType<typeof Person7>
const person7: PersonInstance = new Person7('Alice')

/** 装饰器
  以作用于类本身的装饰器为例：
    接收一个参数 target，表示被装饰的类本身。
    可以返回一个新的类以覆盖被装饰的类，如果不显式返回，则默认返回被装饰的类本身。
  示例：
 **/
function substitute(target: any) {
  console.log(target); // 输出被装饰的类本身
  return class Substitute {
    sayHello() {
      console.log("Hello Decorator");
    }
  };
}

@substitute
class Person8 {
  sayHello() {
    console.log("Hello Class");
  }
}

const person8 = new Person8();
person8.sayHello(); // 输出："Hello Decorator"

function marker(n: number) {
  console.log("装饰器工厂 marker 被调用，参数是 " + n);
  return function (target: any) {
    console.log("装饰器函数被调用，其标记的数字为 " + n);
    target.index = n;
  };
}

@marker(1)
@marker(2)
@marker(3)
class Counter {
  static index: number;
}

console.log("被装饰的类最后得到的静态属性 index 的值为 " + Counter.index);
/* 最终控制台输出如下：
    装饰器工厂 marker 被调用，参数是 1
    装饰器工厂 marker 被调用，参数是 2
    装饰器工厂 marker 被调用，参数是 3
    装饰器函数被调用，其标记的数字为 3
    我是来破坏队形的
    装饰器函数被调用，其标记的数字为 2
    装饰器函数被调用，其标记的数字为 1
    被装饰的类最后得到的静态属性 index 的值为 1
*/

